#include <stdint.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>
#include <fcntl.h>
#include <string.h>
#include <sys/ioctl.h>
#include <linux/types.h>
#include <linux/spi/spidev.h>
#include "w25q256.h"

static const char *device = "/dev/spidev0.0";
static uint8_t mode = 0;
static uint8_t bits = 8;
static uint32_t speed = 1000000;
int fd;
int gpioValuefd;

void spi0_CS0_init()
{
    int exportGpio;
    //write only 只写 只有先向export写入数字，/sys/class/gpio下才能生成gpioXXX文件
	exportGpio = open("/sys/class/gpio/export", O_WRONLY);
	if (exportGpio < 0)
	{
		puts("Cannot open GPIO to export it\n");
		exit(1);
	}
    write(exportGpio, "449", 4);//string -> const char *
    close(exportGpio);

    // Update the direction of the GPIO to be an output
	int directionfd;
    directionfd = open("/sys/class/gpio/gpio449/direction", O_RDWR);
	if (directionfd < 0)
	{
		puts("Cannot open GPIO direction it\n");
		exit(1);
	}
	write(directionfd, "out", 4);
	close(directionfd);
   
    gpioValuefd = open("/sys/class/gpio/gpio449/value", O_RDWR);
    if (gpioValuefd < 0)
	{
		puts("Cannot open GPIO value\n");
		exit(1);
	}
    write(gpioValuefd,"1", 2);
}

void spiInit(void)
{
    int ret = 0;
    fd = open(device, O_RDWR);
    if (fd < 0)
        printf("can't open device\n");

    /* spi mode */
    ret = ioctl(fd, SPI_IOC_WR_MODE, &mode);
    if (ret == -1)
        printf("can't set spi mode\n");

    ret = ioctl(fd, SPI_IOC_RD_MODE, &mode);
    if (ret == -1)
        printf("can't get spi mode\n");

    /* bits per word */
    ret = ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, &bits);
    if (ret == -1)
        printf("can't set bits per word\n");

    ret = ioctl(fd, SPI_IOC_RD_BITS_PER_WORD, &bits);
    if (ret == -1)
        printf("can't get bits per word\n");

    /* max speed hz */
    ret = ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed);
    if (ret == -1)
        printf("can't set max speed hz\n");

    ret = ioctl(fd, SPI_IOC_RD_MAX_SPEED_HZ, &speed);
    if (ret == -1)
        printf("can't get max speed hz\n");

    printf("spi mode: %d\n", mode);
    printf("bits per word: %d\n", bits);
    printf("max speed: %d Hz (%d KHz)\n", speed, speed/1000);
    printf("over!\n");
}

unsigned char SPI_Transfers(unsigned char *tx,unsigned char *rx,unsigned int len)
{
    struct spi_ioc_transfer tr = {
        .tx_buf = (unsigned long)tx,
        .rx_buf = (unsigned long)rx,
        .len = len,
        .delay_usecs = 0,
        .speed_hz = 0,
        .bits_per_word = bits,
    };
    int ret;
    ret = ioctl(fd, SPI_IOC_MESSAGE(1), &tr); //SPI_IOC_MESSAGE(N)，进行N次读写    //ret返回的是成功发送或者接受的字节数
    printf("spi treasfer ret:%d\n",ret);
    if (ret != len)
    {
        printf("can't send spi message\n");
        return -1;
    }
    else
    {
        return 0;
    }
}

void w25q256Init()
{
    int ret = 0;
    ret = W25Q256ReadID();
    if(ret!=0)
    {
        printf("W25Q256 read id error!\n");
        exit(1);
    }
    else
    {
        printf("W25Q256 read id success!\n");
    }

    unsigned char status = W25Q256Read_SR(3); //读取状态寄存器3，判断地址模式
    //如果ADS位置为0 则为三地址模式,所以要设置其为4地址模式
    if((status&0x01) == 0x00)                  
    {
        unsigned char tx = W25Q256_Enable4ByteAddr;
        unsigned char rx = 0;
        spi0CS_low();
        SPI_Transfers(&tx,&rx,1);
        spi0CS_high();
    }
}

//读取芯片ID
unsigned int W25Q256ReadID()
{
    unsigned char tx[] = {0x90,0x00,0x00,0x00,0xFF,0xFF};
    unsigned char rx[6] = {0};
    spi0CS_low();
    SPI_Transfers(tx,rx,6);
    spi0CS_high();
    if((rx[4]==W25Q256_ManufactureID)&&(rx[5]==W25Q256_DeviceID))
    {
        return 0;
    }
    else
    {
        return -1;
    }
}

/**
 * 函数名称: W25Q256_Read_SR
 * 功能描述: 读取W25Q256的状态寄存器
 * 参数说明: regno: [输入] 状态寄存器号，范:1~3
 * 返回说明: 状态寄存器值
 *           状态寄存器1：
 *           BIT7  6   5   4   3   2   1   0
 *           SPR   RV  TB BP2 BP1 BP0 WEL BUSY
 *           SPR:默认0,状态寄存器保护位,配合WP使用
 *           TB,BP2,BP1,BP0:FLASH区域写保护设置
 *           WEL:写使能锁定
 *           BUSY:忙标记位(1,忙;0,空闲)
 *           默认:0x00
 *           状态寄存器2：
 *           BIT7  6   5   4   3   2   1   0
 *           SUS   CMP LB3 LB2 LB1 (R) QE  SRP1
 *           状态寄存器3：
 *           BIT7      6    5    4   3   2   1   0
 *           HOLD/RST  DRV1 DRV0 (R) (R) WPS ADP ADS
 *------------------*
 */
unsigned char W25Q256Read_SR(unsigned char regNo)
{
    unsigned char command = 0;
    switch (regNo)
    {
        case 1:
            command = W25Q256_ReadStatusReg1;
            break;
        case 2:
            command = W25Q256_ReadStatusReg2;
            break;
        case 3:
            command = W25Q256_ReadStatusReg3;
            break;
        default:
            command = W25Q256_ReadStatusReg1;
            break;
    }
    unsigned char tx[] = {command,0xFF};
    unsigned char rx[2] = {0};
    spi0CS_low();
    SPI_Transfers(tx,rx,2);
    spi0CS_high();
    return rx[1];
}

/**
 * 函数名称: W25Q256Read
 * 功能描述: 读取SPI FLASH 在指定地址开始读取指定长度的数据
 * 参数说明: pBuffer: [输出] 数据存储区
 *			     ReadAddr: [输入] 开始读取的地址(24bit)
 *			     NumByteToRead: [输入] 要读取的字节数(最大65535)
 * 返回说明: None
 *
 *------------------*
 */
void W25Q256Read(unsigned char* pBuffer,unsigned int ReadAddr,unsigned int NumByteToRead) 
{
    unsigned char tx[] = {W25Q256_ReadDataReg,(unsigned char)(ReadAddr>>24),(unsigned char)(ReadAddr>>16),\
                            (unsigned char)(ReadAddr>>8),(unsigned char)ReadAddr};
    unsigned char rx[5] = {0};

    spi0CS_low();
    SPI_Transfers(tx,rx,5);

    unsigned char* ptemp;
    ptemp = (unsigned char*)malloc(sizeof(unsigned char)*NumByteToRead);
    memset(ptemp,0xFF,NumByteToRead);

    int leaveByte = NumByteToRead;
    while (leaveByte>64)
    {
        SPI_Transfers(ptemp,pBuffer+(NumByteToRead-leaveByte),64);
        leaveByte-=64;
    }
    SPI_Transfers(ptemp,pBuffer+(NumByteToRead-leaveByte),leaveByte);
    
    spi0CS_high();
    free(ptemp);


    
}

/**
 * 函数名称: W25QXX_Write
 * 功能描述: 写SPI FLASH 在指定地址开始写入指定长度的数据
 * 参数说明: pBuffer: [输入] 数据存储区
 *			     WriteAddr: [输入] 开始写入的地址(24bit)
 *		    	 NumByteToWrite: [输入] 要写入的字节数(最大65535)
 * 更多描述:
 *           写SPI FLASH
 *           在指定地址开始写入指定长度的数据
 *           该函数带擦除操作!
 *------------------*
 */
void W25Q256Write(unsigned char* pBuffer,unsigned int WriteAddr,unsigned int NumByteToWrite)
{
    unsigned int sectorPos = WriteAddr/4096;//第多少个扇区
    unsigned int sectorOff = WriteAddr%4096;//扇区内的偏移
    unsigned int sectorRemain = 4096-sectorOff;//扇区内剩余空间的大小
    unsigned char w25q256Buffer[4096] = {0};

    unsigned int i;
    if(NumByteToWrite<=sectorRemain)
    {
        sectorRemain = NumByteToWrite;  //不大于4096字节，在当前扇区能写完
    }

    while (1)
    {
        W25Q256Read(w25q256Buffer,sectorPos*4096,4096); //读出整个山区的内容
        for(i=0;i<sectorRemain;i++)
        {
            if(w25q256Buffer[sectorOff+i]!=0xFF)
            {
                break;//0xFF为flash默认擦除数据。如果不是则代表有别的写入，需要先擦除在使用
            }
        }
        if(i<sectorRemain) //需要擦除
        {
            W25Q256SectorErase(sectorPos);
            for(i=0;i<sectorRemain;i++)     //拷贝数据到缓冲区
            {
                w25q256Buffer[i+sectorOff] = *(pBuffer+i);
            }
            W25Q256Write_NoCheck(w25q256Buffer,sectorPos*4096,4096);//写入整个扇区
        }
        else
        {
            W25Q256Write_NoCheck(pBuffer,WriteAddr,sectorRemain);//写已经擦除了的,直接写入扇区剩余区间.
        }
        if(NumByteToWrite==sectorRemain)
        {
            break;//写入结束了
        }
        else //写入未结束
        {
            sectorPos++;//扇区地址增1
            sectorOff = 0;//偏移位置为0

            pBuffer+=sectorRemain;//指针偏移
            WriteAddr+=sectorRemain;//写地址偏移
            NumByteToWrite-=sectorRemain;//字节数递减
            if(NumByteToWrite>4096)
            {
                sectorRemain=4096;	//下一个扇区还是写不完
            }
            else
            {
                sectorRemain = NumByteToWrite;//下一个扇区可以写完了
            }
        }
    }
    
}


/**
 * 函数名称: W25QXX_SectorErase
 * 功能描述: 擦除一个扇区
 * 参数说明: Dst_Addr: [输入] 扇区地址 根据实际容量设置
 * 更多描述:
 *           擦除一个扇区的最少时间:150ms
 *------------------*
 */
void W25Q256SectorErase(unsigned int Dst_Addr)
{
    Dst_Addr*=4096;
    W25Q256WriteEnable();    //SET WEL
    W25Q256WaitBusy();       //等待芯片空闲
    unsigned char tx[] = {W25Q256_SectorErase_4bitAddr,(unsigned char)(Dst_Addr>>24),(unsigned char)(Dst_Addr>>16),\
                            (unsigned char)(Dst_Addr>>8),(unsigned char)Dst_Addr};
    unsigned char rx[5] = {0};
    spi0CS_low();
    SPI_Transfers(tx,rx,5);
    spi0CS_high();
    W25Q256WaitBusy();       //等待芯片空闲
}

/**
 * 函数名称: W25QXX_WriteEnable
 * 功能描述: 向FLASH发送 写使能 命令
 * 更多描述: 将WEL置位
 *------------------*
 */
void W25Q256WriteEnable(void)
{
    unsigned char tx[] = {W25Q256_WriteEnable};
    unsigned char rx[1] = {0};
    spi0CS_low();
    SPI_Transfers(tx,rx,1);
    spi0CS_high();
}

/**
 * 函数名称: W25QXX_WaitBusy
 * 功能描述: 等待空闲
 */
void W25Q256WaitBusy(void)
{
    //当编程、擦除或写入状态/安全寄存器指令完成时，BUSY位将被清0，表示器件已准备好执行进一步指令
    spi0CS_low();
    while((W25Q256Read_SR(1)&0x01)==0x01);    //等待BUSY位清空
    spi0CS_high();
}

/**
 * 函数名称: W25QXX_Write_NoCheck
 * 功能描述: 无检验写SPI FLASH
 * 参数说明: pBuffer: [输入] 数据存储区
 *		     	 WriteAddr: [输入] 开始写入的地址(24bit)
 *		    	 NumByteToWrite: [输入] 要写入的字节数(最大65535)
 * 更多描述:
 *           无检验写SPI FLASH
 *           必须确保所写的地址范围内的数据全部为0XFF,否则在非0XFF处写入的数据将失败!
 *           具有自动换页功能
 *           在指定地址开始写入指定长度的数据,但是要确保地址不越界!
 *------------------*
 */
void W25Q256Write_NoCheck(unsigned char* pBuffer,unsigned int WriteAddr,unsigned int NumByteToWrite)
{
    unsigned int pageremain;
    pageremain=256-WriteAddr%256; //单页剩余的字节数
    if(NumByteToWrite<=pageremain) {
        pageremain=NumByteToWrite;//不大于256个字节
    }
    while(1)
    {
        W25Q256Write_Page(pBuffer,WriteAddr,pageremain);
        if(NumByteToWrite == pageremain)
        {
            break; //写入结束
        }
        else
        {
            pBuffer+=pageremain;
            WriteAddr+=pageremain;
            NumByteToWrite-=pageremain;			  //减去已经写入了的字节数
            if(NumByteToWrite>256)
            {
                pageremain=256; //一次可以写入256个字节
            }
            else
            {
                pageremain=NumByteToWrite; 	  //不够256个字节了
            }
        }
    }
}

/**
 * 函数名称: W25QXX_Write_Page
 * 功能描述: SPI在一页(0~65535)内写入少于256个字节的数据
 * 参数说明: pBuffer: [输入] 数据存储区
 *    			 WriteAddr: [输入] 开始写入的地址(24bit)
 *			     NumByteToWrite: [输入] 要写入的字节数(最大256),该数不应该超过该页的剩余字节数!!!
 * 更多描述:
 *           在指定地址开始写入最大256字节的数据
 *------------------*
 */
void W25Q256Write_Page(unsigned char* pBuffer,unsigned int WriteAddr,unsigned int NumByteToWrite)
{
    W25Q256WriteEnable(); //SET WEL
    unsigned char tx[] = {W25Q256_PageProgram,(unsigned char)(WriteAddr>>24),(unsigned char)(WriteAddr>>16),\
                        (unsigned char)(WriteAddr>>8),(unsigned char)WriteAddr};
    unsigned char rx[5] = {0};
    spi0CS_low();
    SPI_Transfers(tx,rx,5);

    unsigned char* ptemp;
    ptemp = (unsigned char*)malloc(sizeof(unsigned char)*NumByteToWrite);
    memset(ptemp,0,NumByteToWrite);

    int leaveByte = NumByteToWrite;
    while (leaveByte>64)
    {
        SPI_Transfers(pBuffer,ptemp+(NumByteToWrite-leaveByte),64);
        leaveByte-=64;
    }
    SPI_Transfers(pBuffer,ptemp+(NumByteToWrite-leaveByte),leaveByte);
    spi0CS_high();

    free(ptemp);
    W25Q256WaitBusy();            //等待写入结束
}

void spi0CS_high()
{
    write(gpioValuefd,"1", 2);
}
void spi0CS_low()
{
    write(gpioValuefd,"0", 2);
}